﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;

namespace Infrastructure
{
    public class TypeLoader
    {
        #region Data Members

        private readonly string _applicationPath;
        private Dictionary<Type, List<Type>> _cache;

        private static TypeLoader _instance;
        private static readonly object SyncObject = new object();

        #endregion

        public static TypeLoader Instance
        {
            get
            {
                if (_instance == null)
                {
                    lock (SyncObject)
                    {
                        if (_instance == null)
                        {
                            _instance = new TypeLoader();
                        }
                    }
                }
                return _instance;
            }

        }

        public TypeLoader ()
        {
            _applicationPath = Environment.CurrentDirectory;
            _cache = new Dictionary<Type, List<Type>>();
        }

        public string[] GetTypesNames ( Type type )
        {
            if (!_cache.ContainsKey( type ))
            {
                LoadTypes( type );
            }
            return _cache[type].Select( t => t.AssemblyQualifiedName ).ToArray();
        }

        public Type[] GetTypes ( Type type )
        {
            if (!_cache.ContainsKey( type ))
            {
                LoadTypes( type );
            }
            return _cache[type].ToArray();
        }

        public bool HasType ( Type type )
        {
            foreach (var cash in _cache)
            {
                foreach (var type1 in cash.Value)
                {
                    if (type == type1)
                    {
                        return true;
                    }
                }
            }
            return false;
        }

        #region Private Methods

        private void LoadTypes ( Type type ,bool getAbstractTypes = true)
        {
            var files = Directory.GetFiles( _applicationPath );

            if (!_cache.ContainsKey( type ))
            {
                _cache.Add( type, new List<Type>() );
            }

            foreach (var file in files)
            {
                string extention = Path.GetExtension( file );
                if (extention == ".dll" || extention == ".exe")
                {
                    try
                    {
                        LoadTypes( file, type, getAbstractTypes );
                    }
                    catch (Exception ex)
                    {
                        //log.Info( string.Format( "Failed to load file: {0}", file ), ex );
                    }
                }
            }

        }

        private void LoadTypes ( string file, Type type, bool getAbstractTypes )
        {
            var assembly = Assembly.LoadFrom( file );
            var assemblyTypes = assembly.GetTypes();
            Assembly.LoadFrom( file );

            foreach (var assemblyType in assemblyTypes)
            {
                if (assemblyType.IsSubclassOf( type ) || ( type.IsClass && ( assemblyType == type && !assemblyType.IsAbstract ) ))
                {
                    _cache[type].Add( assemblyType );
                }
                else if (type.IsInterface && (getAbstractTypes && !assemblyType.IsAbstract))
                {
                    var implementedInterfaces = assemblyType.GetInterfaces();

                    foreach (var implementedInterface in implementedInterfaces)
                    {
                        if (implementedInterface == type)
                        {
                            _cache[type].Add( assemblyType );
                            break;
                        }
                    }
                }

            }

        }

        #endregion

    }
}